文章同步發表至 Medium
理解了為甚麼會一直出現這個型別轉換的錯誤之後,接下來就是要動手,把原始碼調整成可以處理 Polygon 的形狀(?)。
輸出 Shapefile 所使用的方法是 Shapefile.WriteAllFeatures()
,在 Shapefile.cs
中的原始碼如下:
public static void WriteAllFeatures(IEnumerable<IFeature> features, string shpPath, Encoding encoding = null, string projection = null)
{
if (features == null)
throw new ArgumentNullException(nameof(features));
var firstFeature = features.FirstOrDefault();
if (firstFeature == null)
throw new ArgumentException(nameof(ShapefileWriter) + " requires at least one feature to be written.");
var fields = firstFeature.Attributes.GetDbfFields();
var shapeType = features.FindNonEmptyGeometry().GetShapeType();
using (var shpWriter = OpenWrite(shpPath, shapeType, fields, encoding, projection))
{
shpWriter.Write(features);
}
}
在第 13 行中所使用的 OpenWrite
方法:
public static ShapefileWriter OpenWrite(string shpPath, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null, string projection = null)
{
if (type.IsPoint())
{
return new ShapefilePointWriter(shpPath, type, fields, encoding, projection);
}
else if (type.IsMultiPoint())
{
return new ShapefileMultiPointWriter(shpPath, type, fields, encoding, projection);
}
else if (type.IsPolyLine())
{
return new ShapefilePolyLineWriter(shpPath, type, fields, encoding, projection);
}
else if (type.IsPolygon())
{
return new ShapefilePolygonWriter(shpPath, type, fields, encoding, projection);
}
else
{
throw new FileLoadException("Unsupported shapefile type: " + type, shpPath);
}
}
可以看到第 17 行,如果是 Polygon
就會建立一個新的 ShapefilePolygonWriter
,而他是繼承 ShapefileWriter
的 MultiPolygon
型別。第 21 行所回傳的 ShpPolygonWriter
也是,繼承 ShpWriter
,泛型的型別一樣是 MultiPolygon
,可以參考 GitHub 的原始碼。
public class ShapefilePolygonWriter : ShapefileWriter<MultiPolygon>
{
/// <inheritdoc/>
public ShapefilePolygonWriter(Stream shpStream, Stream shxStream, Stream dbfStream, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null)
: base(shpStream, shxStream, dbfStream, type, fields, encoding)
{ }
/// <inheritdoc/>
public ShapefilePolygonWriter(string shpPath, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null, string projection = null)
: base(shpPath, type, fields, encoding, projection)
{ }
/// <inheritdoc/>
public ShapefilePolygonWriter(string shpPath, ShapeType type, params DbfField[] fields)
: base(shpPath, type, fields)
{
}
internal override ShpWriter<MultiPolygon> CreateShpWriter(Stream shpStream, Stream shxStream)
{
return new ShpPolygonWriter(shpStream, shxStream, ShapeType);
}
我的想法很簡單,既然他把所有 Polygon 類型的 Geometry 都交由 MultiPolygonWriter 處理,那我就把 Polygon 單獨取出來,另外寫一個新的方式處理就好。
針對我遇到的問題,我有在 GitHub 上提問,有人提供了另外一個解法:
var geometry = wktReader.Read(...);
// 指定 Geometry 的型別
var poly = (Polygon) geometry;
// 利用 Polygon 製作一個新的 MultiPolygon
var acceptableGeometry = poly.Factory.CreateMultiPolygon(new[] {poly});
Shapefile.WriteAllFeatures(...);
相較之下的確簡單了許多,但在實務上,如果需要和第三方接洽,可能就需要確認是否對方能接受 MultiPolygon 的格式。
從我們剛剛所觀察的原始碼可以看到,需要修正的檔案有三個:
ShapeType.cs
:把 IsPolygon()
和 IsMultiPolygon()
拆開Shapefile.cs
:依據 IsPolygon()
和 IsMultiPolygon()
分別回傳不同的 WriterShapefilePolygonWriter.cs
:拆成兩種 Polygon WrtierShpPolygonWriter.cs
:拆成兩種 Polygon Wrtier下面會附上需要新增的 Polygon 相關檔案,記得同時也要把原本的檔名修改成 MultiPolygon
,編譯時才不會打架。修改完成的版本我有拉一個 fork 在我的 GitHub,有需要的話也可以直接前往下載。
ShapeType.cs
修改的重點在於把 Polygon
和 MultyPolygon
分開,實際操作上我比較不會使用到 PolygonM
、PolygonZM
這兩個型別,有需要的話也可以自行拆解看看。
/// <summary>
/// Indicates if shape associated with this type is a Polygon.
/// </summary>
public static bool IsPolygon(this ShapeType type)
{
// 修改成只符合 Polygon
return type == ShapeType.Polygon;
}
/// <summary>
/// Indicates if shape associated with this type is a Multi Polygon.
/// </summary>
public static bool IsMultiPolygon(this ShapeType type)
{
// 將其他 Polygon 類別歸到 MultiPolygon
return type == ShapeType.PolygonM
|| type == ShapeType.PolygonZM
|| type == ShapeType.PolygonZ;
}
Shapefile.cs
public static ShapefileWriter OpenWrite(string shpPath, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null, string projection = null)
{
if (type.IsPoint())
{
return new ShapefilePointWriter(shpPath, type, fields, encoding, projection);
}
else if (type.IsMultiPoint())
{
return new ShapefileMultiPointWriter(shpPath, type, fields, encoding, projection);
}
else if (type.IsPolyLine())
{
return new ShapefilePolyLineWriter(shpPath, type, fields, encoding, projection);
}
else if (type.IsPolygon())
{
// 新增 IsMultiPolygon() 的判斷,回傳對應的 Wtiter
return new ShapefilePolygonWriter(shpPath, type, fields, encoding, projection);
}
else if (type.IsMultiPolygon())
{
// 新增 IsMultiPolygon() 的判斷,回傳對應的 Wtiter
return new ShapefileMultiPolygonWriter(shpPath, type, fields, encoding, projection);
}
else
{
throw new FileLoadException("Unsupported shapefile type: " + type, shpPath);
}
}
ShapefilePolygonWriter.cs
public class ShapefilePolygonWriter : ShapefileWriter<Polygon> // 型別改成 Polygon
{
/// <inheritdoc/>
public ShapefilePolygonWriter(Stream shpStream, Stream shxStream, Stream dbfStream, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null)
: base(shpStream, shxStream, dbfStream, type, fields, encoding)
{ }
/// <inheritdoc/>
public ShapefilePolygonWriter(string shpPath, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null, string projection = null)
: base(shpPath, type, fields, encoding, projection)
{ }
/// <inheritdoc/>
public ShapefilePolygonWriter(string shpPath, ShapeType type, params DbfField[] fields)
: base(shpPath, type, fields)
{
}
internal override ShpWriter<Polygon> CreateShpWriter(Stream shpStream, Stream shxStream)
{
// 回傳 Polygon 的 Writer
return new ShpPolygonWriter(shpStream, shxStream, ShapeType);
}
}
ShpPolygonWriter.cs
這邊要注意的是要如何把 Geometry
寫入。這邊使用的方法是參考 MultiPolygon
,只是把迴圈的部分拿掉。
public class ShpPolygonWriter : ShpWriter<Polygon>
{
/// <inheritdoc/>
public ShpPolygonWriter(Stream shpStream, Stream shxStream, ShapeType type) : base(shpStream, shxStream, type)
{
if (!ShapeType.IsPolygon())
ThrowUnsupportedShapeTypeException();
}
internal override void WriteGeometry(Polygon polygon, Stream stream)
{
var partsBuilder = new ShpMultiPartBuilder(1, 4);
partsBuilder.AddPart(polygon.Shell.CoordinateSequence);
partsBuilder.WriteParts(stream, HasZ, HasM);
partsBuilder.UpdateExtent(Extent);
}
}